package database

import (
	"abdis/interface/database"
	"abdis/interface/redis"
	"abdis/redis/protocol"
	"strconv"
	"time"
)

func (db *DB) getAsString(key string) ([]byte, protocol.ErrorReply) {
	entity, ok := db.GetEntity(key)
	if !ok {
		return nil, nil
	}
	bytes, ok := entity.Data.([]byte)
	if !ok {
		return nil, protocol.MakeWrongTypeErrReply()
	}
	return bytes, nil
}

func execGet(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	bytes, err := db.getAsString(key)
	if err != nil {
		return err, false
	}
	if bytes == nil {
		return protocol.MakeNullBulkReply(), false
	}
	return protocol.MakeBulkReply(bytes), true
}

func execGetDel(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	bytes, err := db.getAsString(key)
	if err != nil {
		return err, false
	}
	if bytes == nil {
		return protocol.MakeNullBulkReply(), false
	}
	db.Remove(key)

	//db.addAof(utils.ToCmdLineRaw("Del", args...))
	return protocol.MakeBulkReply(bytes), true
}

func execSet(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	value := args[1]

	if len(args) > 2 {
		// other options
		// TODO
	}

	entity := &database.DataEntity{
		Data: value,
	}

	// 这个返回的是新增的数据量
	result := db.PutEntity(key, entity)

	if result == 0 {
		db.Persist(key) // 清除之前设置的过期
	} else {
		//db.addAof(utils.ToCmdLineRaw("Set", args...))
	}
	return protocol.MakeOkReply(), true

	//return protocol.MakeNullBulkReply()
}

func execSetNX(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	entity := &database.DataEntity{Data: args[1]}
	ret := db.PutIfAbsent(key, entity)
	//if ret > 0 {
	//	db.addAof(utils.ToCmdLineRaw("Set", args...))
	//}
	return protocol.MakeIntReply(int64(ret)), true
}

// SETEX key seconds value
func execSetEX(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	entity := &database.DataEntity{Data: args[2]}
	ttlArg, err := strconv.ParseInt(string(args[1]), 10, 64)
	if err != nil {
		return protocol.MakeSyntaxErrReply(), false
	}
	if ttlArg < 0 {
		return protocol.MakeErrReply("ERR invalid expire time"), false
	}
	expireTime := time.Now().Add(time.Duration(ttlArg*1000) * time.Millisecond)

	db.PutEntity(key, entity)
	db.Expire(key, expireTime)

	//db.addAof(utils.ToCmdLineRaw("SetEX", args...))

	return protocol.MakeOkReply(), true
}

func deltaBy(db *DB, key string, delta int64) (redis.Reply, bool) {
	bytes, errReply := db.getAsString(key)
	if errReply != nil {
		return errReply, false
	}
	if bytes == nil {
		bytes = []byte("0")
	}
	val, err := strconv.ParseInt(string(bytes), 10, 64)
	if err != nil {
		return protocol.MakeErrReply("ERR value is not an integer"), false
	}
	val += delta
	entity := &database.DataEntity{Data: []byte(strconv.FormatInt(val, 10))}
	db.PutEntity(key, entity)
	//db.addAof(utils.ToCmdLineRaw("IncrBy", []byte(key), []byte(strconv.FormatInt(delta, 10))))
	return protocol.MakeIntReply(val), true
}

func execInr(db *DB, args [][]byte) (redis.Reply, bool) {
	return deltaBy(db, string(args[0]), 1)
}

func execInrBy(db *DB, args [][]byte) (redis.Reply, bool) {
	delta, err := strconv.ParseInt(string(args[1]), 10, 64)
	if err != nil {
		return protocol.MakeErrReply("ERR value is not an integer"), false
	}
	return deltaBy(db, string(args[0]), delta)
}

func execDecr(db *DB, args [][]byte) (redis.Reply, bool) {
	return deltaBy(db, string(args[0]), -1)
}

func execDecrBy(db *DB, args [][]byte) (redis.Reply, bool) {
	delta, err := strconv.ParseInt(string(args[1]), 10, 64)
	if err != nil {
		return protocol.MakeErrReply("ERR value is not an integer"), false
	}
	return deltaBy(db, string(args[0]), -delta)
}

func init() {
	registerCommand("Get", execGet, readFirstKey, nil, 2, flagReadOnly)
	registerCommand("GetDel", execGetDel, writeFirstKey, rollbackFirstKey, 2, flagWrite)

	registerCommand("Set", execSet, writeFirstKey, rollbackFirstKey, -3, flagWrite)

	registerCommand("SetNX", execSetNX, writeFirstKey, rollbackFirstKey, 3, flagWrite)

	registerCommand("SetEX", execSetEX, writeFirstKey, rollbackFirstKey, 4, flagWrite|flagExpire)

	registerCommand("Incr", execInr, writeFirstKey, rollbackFirstKey, 2, flagWrite)
	registerCommand("IncrBy", execInrBy, writeFirstKey, rollbackFirstKey, 3, flagWrite)
	registerCommand("Decr", execDecr, writeFirstKey, rollbackFirstKey, 2, flagWrite)
	registerCommand("DecrBy", execDecrBy, writeFirstKey, rollbackFirstKey, 3, flagWrite)
}
